package org.pf4j.spring;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.pf4j.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.io.File;
import java.nio.file.Path;
import java.util.Map;
import java.util.Set;

public class SpringPluginManager extends DefaultPluginManager implements ApplicationContextAware {

    private Logger log = LoggerFactory.getLogger(SpringPluginManager.class);

    private ApplicationContext applicationContext;

    public SpringPluginManager() {
    }

    public SpringPluginManager(Path pluginsRoot) {
        super(pluginsRoot);
    }

    @Override
    protected ExtensionFactory createExtensionFactory() {
        return new SingletonSpringExtensionFactory(this);
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = applicationContext;
    }

    public ApplicationContext getApplicationContext() {
        return applicationContext;
    }


    @PostConstruct
    public void init() {
        log.info("开始启动SpringPluginManager");
        loadPlugins();
        startPlugins();

        AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory) applicationContext.getAutowireCapableBeanFactory();
        ExtensionsInjector extensionsInjector = new ExtensionsInjector(this, beanFactory);
        extensionsInjector.injectExtensions();
    }

    @PreDestroy
    public void destroy() {
        log.info("开始销毁SpringPluginManager");
        stopPlugins();
    }

    protected PluginWrapper loadPluginFromPath(Path filePath) {
        PluginWrapper pluginWrapper = super.loadPluginFromPath(filePath);
        this.resolvePlugins();
        return pluginWrapper;
    }

    public String loadZipFileFromPathAutoStart(String filePath) throws Exception {
        if (StringUtils.isBlank(filePath)) {
            throw new NullPointerException("filePath is required");
        }
        PluginWrapper plugin = null;
        try {
            File file = new File(filePath);
            plugin = this.loadPluginFromPath(file.toPath());
            this.startPlugin(plugin.getPluginId());
            log.info("Registering extensions of the plugin '{}' as beans", plugin.getPluginId());
            Set<String> extensionClassNames = this.getExtensionClassNames(plugin.getPluginId());
            for (String extensionClassName : extensionClassNames) {
                log.info("Register extension '{}' as bean", extensionClassName);
                Class<?> extensionClass = plugin.getPluginClassLoader().loadClass(extensionClassName);
                Map<String, ?> extensionBeanMap = this.applicationContext.getBeansOfType(extensionClass);
                if (extensionBeanMap.isEmpty()) {
                    Object extension = this.getExtensionFactory().create(extensionClass);
                    AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory)
                            applicationContext.getAutowireCapableBeanFactory();
                    beanFactory.registerSingleton(extensionClass.getName(), extension);
                } else {
                    log.info("Bean registeration aborted! Extension '{}' already existed as bean!", extensionClass.getName());
                }
            }
        } catch (Exception e) {
            log.error(ExceptionUtils.getStackTrace(e));
            if (plugin != null){
                this.plugins.remove(plugin.getPluginId());
                PluginWrapper finalPlugin = plugin;
                this.getUnresolvedPlugins().removeIf(pluginWrapper -> pluginWrapper.equals(finalPlugin));
                this.getPluginClassLoaders().remove(plugin.getPluginId());
            }
            throw new Exception(e.getMessage());
        }
        return plugin.getPluginId();
    }

    @Override
    public boolean deletePlugin(String pluginId) {
        PluginWrapper pluginWrapper = this.getPlugin(pluginId);
        // delete SingletonFactory cache
        SingletonSpringExtensionFactory extensionFactory = (SingletonSpringExtensionFactory) this.getExtensionFactory();
        Set<String> extensionClassNames = this.getExtensionClassNames(pluginWrapper.getPluginId());
        for (String extensionClassName : extensionClassNames) {
            log.info("delete '{}' bean", extensionClassName);
            AbstractAutowireCapableBeanFactory beanFactory = (AbstractAutowireCapableBeanFactory)
                    applicationContext.getAutowireCapableBeanFactory();
            if (beanFactory.containsSingleton(extensionClassName)) {
                beanFactory.destroySingleton(extensionClassName);
            }
            extensionFactory.delete(extensionClassName);
        }

        PluginState pluginState = stopPlugin(pluginId);
        if (PluginState.STARTED == pluginState) {
            log.error("Failed to stop plugin '{}' on delete", pluginId);
            return false;
        } else {
            Plugin plugin = pluginWrapper.getPlugin();
            if (!this.unloadPlugin(pluginId)) {
                log.error("Failed to unload plugin '{}' on delete", pluginId);
                return false;
            } else {
                plugin.delete();
                return true;
            }
        }
    }
}
